#include "WavesMesh.h"

extern "C" void WavesUpdate(float time, int size, int numWaves, cudaGraphicsResource* cudaVBO, cudaGraphicsResource* cudaNor, WavesData* wd);

WavesMesh::WavesMesh()
{
	 time = 0;
	 size = 300;
	 numWaves = 100;

	 wd = new WavesData[numWaves];
	 for (int i = 0; i < numWaves; i++)
	 {
		wd[i].waveLength = 0.2f * ((rand() % 25+5) / 10.f);
		wd[i].amplitude = 0.0005f *((rand() % 10 + 5) / 10.f);
		wd[i].speed = 0.03f * ((rand() % 15 + 5) / 10.f);
		wd[i].k = rand() % 5+1;
		wd[i].direction = Vector3(-rand() % 10-10, rand() % 20-10, 0);
		wd[i].direction.Normalise();
	 }

	 GenerateWaves();
	 cudaGLSetGLDevice(1);
	 if (cudaGraphicsGLRegisterBuffer(&cudaVBO, bufferObject[VERTEX_BUFFER], cudaGraphicsMapFlagsNone) != cudaSuccess)
		 printf("Failed\n");
	 if (cudaGraphicsGLRegisterBuffer(&cudaNor, bufferObject[NORMAL_BUFFER], cudaGraphicsMapFlagsNone) != cudaSuccess)
		 printf("Failed\n");
}


WavesMesh::~WavesMesh()
{
	if (cudaGraphicsUnregisterResource(cudaVBO) != cudaSuccess)
		printf("Failed\n");
	if (cudaGraphicsUnregisterResource(cudaNor) != cudaSuccess)
		printf("Failed\n");
}


void WavesMesh::GenerateWaves()
{
	int np = size;
	int nv = np*np;
	numVertices = nv;
	float* verts = new float[3 * nv];
	float* norms = new float[3 * nv];
	float* tcoords= new float[2 * nv];

	vertices = new Vector3[nv];
	textureCoords = new Vector2[nv];
	normals = new Vector3[nv];

	unsigned int* indices = new unsigned int[nv];

	numIndices = (np - 1) * (np - 1) * 6;
	indices = new unsigned int[numIndices];

	for (int i = 0; i < np; i++)
	{
		for (int j = 0; j < np; j++)
		{
			int c = i*np + j;
			vertices[c] = Vector3(1.0 / np * j, -1.0 / np * i, 0);
			textureCoords[c] = Vector2(vertices[c].x, vertices[c].y);
			normals[c] = Vector3(0,0,1);

			verts[3 * c] = vertices[c].x;
			verts[3 * c + 1] = vertices[c].y;
			verts[3 * c + 2] = vertices[c].z;

			tcoords[2 * c] = textureCoords[c].x;
			tcoords[2 * c + 1] = textureCoords[c].y;

			norms[3 * c] = normals[c].x;
			norms[3 * c + 1] = normals[c].y;
			norms[3 * c + 2] = normals[c].z;
		}
	}

	type = GL_TRIANGLES;

	for (int i = 0; i < np-1; i++)
	{
		for (int j = 0; j < np-1; j++)
		{
			indices[(i*(np - 1) + j) * 6] = i*np + j;			
			indices[(i*(np - 1) + j) * 6 + 1] = (i + 1)*np + j;		
			indices[(i*(np - 1) + j) * 6 + 2] = i*np + j + 1;		
			indices[(i*(np - 1) + j) * 6 + 3] = i*np + j + 1;		
			indices[(i*(np - 1) + j) * 6 + 4] = (i + 1)*np + j;
			indices[(i*(np - 1) + j) * 6 + 5] = (i + 1)*np + j + 1;
		}
	}
	restart_index = numVertices;

	glBindVertexArray(arrayObject);

	InitVBO(VERTEX_BUFFER, (float*)verts, 3, numVertices, GL_DYNAMIC_DRAW);
	InitVBO(NORMAL_BUFFER, (float*)norms, 3, numVertices, GL_DYNAMIC_DRAW);
	InitVBO(TEXTURE_BUFFER, (float*)tcoords, 2, numVertices, GL_DYNAMIC_DRAW);
	InitIBO(indices, numIndices, GL_DYNAMIC_DRAW);

	glBindVertexArray(0);

	delete[] verts;
	delete[] norms;
	delete[] tcoords;
	delete[] indices;
}


void WavesMesh::InitVBO(MeshBuffer type, float* data, int comps, int num, unsigned int mode) {
	glGenBuffers(1, &bufferObject[type]);
	glBindBuffer(GL_ARRAY_BUFFER, bufferObject[type]);
	glBufferData(GL_ARRAY_BUFFER, num*comps*sizeof(GLfloat), (GLvoid*)data, mode);
	glVertexAttribPointer((GLuint)type, comps, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray((GLuint)type);
}

void WavesMesh::InitIBO(unsigned int* data, int num, unsigned int mode) {
	numIndices = num;
	glGenBuffers(1, &bufferObject[INDEX_BUFFER]);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferObject[INDEX_BUFFER]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, num * sizeof(GLuint), (GLvoid*)data, mode);
}

void WavesMesh::Draw(bool update){
	time = time + 0.016;

	WavesUpdate(time, size, numWaves, cudaVBO, cudaNor, wd);
	
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture);

	glPrimitiveRestartIndex(restart_index);
	glEnable(GL_PRIMITIVE_RESTART);
	glBindVertexArray(arrayObject);
	glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);
	glBindVertexArray(0);
	glDisable(GL_PRIMITIVE_RESTART);

	if (memoryMark)
	{
		OGLRenderer::memoryFoot = OGLRenderer::memoryFoot + numVertices * 20.f * 4.f / 1024.f;
		memoryMark = false;
	}
}